﻿using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Source.Plugins
{
    /// <summary>
    /// On create or assign of a record, share the record with ALL child business units.
    /// This allows you to provide 'Child: Parent Business Units' access to any user-owned entities you want.
    /// 
    /// Plugin Steps:
    /// create - post - sync
    /// update - post - sync - filtering: owningbusinessunit
    /// 
    /// Note: register on whichever user owned entities you want
    /// </summary>
    class ShareWithChildBUs
    {
        private IOrganizationService _service;

        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            _service = factory.CreateOrganizationService(null); // Run in system context as users may have Assign but not Share

            if (!context.InputParameters.Contains("Target")) { return; }
            Entity target = context.InputParameters["Target"] as Entity;
            if (target == null) { return; }

            if (target.Contains("owningbusinessunit") && target.GetAttributeValue<EntityReference>("owningbusinessunit") != null)
            {
                Share(target, context);
            }
        }

        private void Share(Entity target, IPluginExecutionContext context)
        {
            EntityReference targetRef = target.ToEntityReference();
            EntityReference owningBU = target.GetAttributeValue<EntityReference>("owningbusinessunit");

            if (context.MessageName.Equals("update", StringComparison.InvariantCultureIgnoreCase))
            {
                // Unshare from all existing teams (if you don't want all existing teams removed on reassign you can comment this)
                RevokeAccessFromTeams(targetRef);
            }

            // Share with ALL child business units
            ShareWithChildBusinessUnits(targetRef, owningBU.Id);
        }

        private void RevokeAccessFromTeams(EntityReference target)
        {
            // Get all the users/teams the record is already shared with
            var sharedWith = (RetrieveSharedPrincipalsAndAccessResponse)_service.Execute(new RetrieveSharedPrincipalsAndAccessRequest
            {
                Target = target
            });

            if (sharedWith != null && sharedWith.PrincipalAccesses != null)
            {
                sharedWith.PrincipalAccesses.ToList().ForEach(a =>
                {
                    // Only unshare teams
                    if (a.Principal.LogicalName.Equals("team", StringComparison.InvariantCultureIgnoreCase))
                    {
                        // Revoke access from the team
                        _service.Execute(new RevokeAccessRequest
                        {
                            Target = target,
                            Revokee = a.Principal
                        });
                    }
                });
            }
        }

        private void ShareWithChildBusinessUnits(EntityReference target, Guid businessUnitId)
        {
            // Get child teams and share with them
            List<Entity> childTeams = GetChildBusinessUnitTeams(businessUnitId);
            childTeams.ForEach(a =>
            {
                Guid childBusinessUnitId = (Guid)((AliasedValue)a["bu.businessunitid"]).Value; // The child business unit
                EntityReference teamReference = new EntityReference("team", a.Id); // The default team of the child business unit

                // Grant access for the team
                _service.Execute(new GrantAccessRequest
                {
                    Target = target,
                    PrincipalAccess = new PrincipalAccess
                    {
                        // Only givesa Read and Write access (this can be modified as needed)
                        AccessMask = AccessRights.ReadAccess | AccessRights.WriteAccess,
                        Principal = teamReference
                    }
                });

                // Call this method for each child business unit to also share with all their children
                // If you only want users to see one level up, comment this line
                ShareWithChildBusinessUnits(target, childBusinessUnitId);
            });
        }

        private List<Entity> GetChildBusinessUnitTeams(Guid businessUnitId)
        {
            List<Entity> teams = new List<Entity>();

            EntityCollection collection = _service.RetrieveMultiple(new QueryExpression("team")
            {
                Criteria =
                {
                    Conditions =
                    {
                        // We only want the default team, which contains all users of the business unit
                        new ConditionExpression("isdefault", ConditionOperator.Equal, true)
                    }
                },
                LinkEntities =
                {
                    new LinkEntity()
                    {
                        LinkFromEntityName = "team",
                        LinkToEntityName = "businessunit",
                        LinkFromAttributeName = "businessunitid",
                        LinkToAttributeName = "businessunitid",
                        EntityAlias = "bu",
                        Columns = new ColumnSet("businessunitid"),
                        LinkCriteria =
                        {
                            Conditions =
                            {
                                // This will give all children for the selected Business Unit
                                new ConditionExpression("parentbusinessunitid", ConditionOperator.Equal, businessUnitId)
                            }
                        }
                    }
                }
            });

            if (collection != null && collection.Entities.Count > 0)
            {
                teams = collection.Entities.ToList();
            }

            return teams;
        }
    }
}
